package com.example.codegen;

import com.example.utils.CJUtils;
import com.example.utils.classNode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;

public class Klass implements AstNode {
    public boolean isInterface = false;
    public String className;
    public Optional<Field> Super;
    public Field[] fields;
    public Method[] methods;

    public Klass(String className, Field[] fields, Method[] methods) {
        this.className = className;
        if (fields.length > 0 && fields[0].fieldName.identifier.equals("super")) {
            this.Super = Optional.of(fields[0]);
            this.fields = Arrays.copyOfRange(fields, 1, fields.length);
        } else {
            this.Super = Optional.empty();
            this.fields = fields;
        }
        this.methods = methods;
    }

    public String toJava() {
        return "class " + className + Super.map(s -> " extends " + s.fieldType.toJava()).orElse("") + " {\n" + String.join("\n", new ArrayList<String>() {{
            for (Field field : fields) {
                add(field.toJava());
            }
            for (Method method : methods) {
                add(method.toJava());
            }
        }}) + "\n}";
    }

    @Override
    public String toGo() {
        // for golang, we use a new STATICX struct to store all static fields
        var static_fields = Arrays.stream(fields).filter(field -> field.isStatic).toArray(Field[]::new);
        var non_static_fields = Arrays.stream(fields).filter(field -> !field.isStatic).toArray(Field[]::new);
        var static_fields_str = static_fields.length == 0 ? "" : "type ST" + className.toUpperCase() + " struct {\n" + String.join("\n", new ArrayList<String>() {{
            for (Field field : static_fields) {
                add(field.toGo());
            }
        }}) + "\n}\n" + "var STATIC" + className.toUpperCase() + " = ST" + className.toUpperCase() + "{}\n\n";

        // golang use upper case (leading character) for public Struct
        // for struct without non-static fields, we still need an empty struct to define the type
        var non_static_fields_str = "type " + className.toUpperCase() + " struct {\n" + String.join("\n", new ArrayList<String>() {{
            for (Field field : non_static_fields) {
                add(field.toGo());
            }
        }}) + "\n}\n";

        return static_fields_str + non_static_fields_str;
    }

    @Override
    public String toJs() {
        return "class " + className + Super.map(s -> " extends" + s.fieldType.toJs()).orElse("") + " {\n" + String.join("\n", new ArrayList<String>() {{
            for (Field field : fields) {
                if (field.isStatic) {
                    add(field.toJs());
                }
            }
            add("constructor() {");
            Super.ifPresent(s -> add("super();"));
            for (Field field : fields) {
                if (!field.isStatic) {
                    add(field.toJs());
                }
            }
            add("}");
            for (Method method : methods) {
                add(method.toJs());
            }
        }}) + "\n}";
    }

    @Override
    public String toCSharp() {
        return "class " + className.replace('$', '_') + Super.map(s -> " : " + s.fieldType.toCSharp()).orElse("") + " {\n" + String.join("\n", new ArrayList<String>() {{
            for (Field field : fields) {
                add(field.toCSharp());
            }
            for (Method method : methods) {
                add(method.toCSharp());
            }
        }}) + "\n}";
    }

    public String toCJ(){
        if(Super.isPresent()){
            Field s = Super.get();
            String parentName = s.fieldType.toCJ().charAt(0)=='?' ? s.fieldType.toCJ().substring(1) : s.fieldType.toCJ();
            CJUtils.classNodes.put(className.toLowerCase(), new classNode(className, parentName));
        } else {
            CJUtils.classNodes.put(className.toLowerCase(), new classNode(className, null));
        }
        Set<Field> fieldName = CJUtils.classNodes.get(className.toLowerCase()).fields;
        for(Field field: fields){
            fieldName.add(field);
        }
        return "public open class " + className.replace('$', '_') + Super.map(s -> " <: " + (s.fieldType.toCJ().charAt(0)=='?' ? s.fieldType.toCJ().substring(1) : s.fieldType.toCJ()).replace('$', '_')).orElse("") + " {\n" + String.join("\n", new ArrayList<String>() {{
            for (Field field : fields) {
                add(field.toCJ());
            }
            for (Method method : methods) {
                add(method.toCJ());
            }
        }}) + "\n}\n";
    }
}
